/*
	Package epd implements parsing of chess positions in
	Extended Position Description.

	Steven Edwards specified the EPD standard for computer chess applications
	as part	of the Portable Game Notation (PGN), see
	http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm#c16.2

	The chess programming wiki has a (much shorter) translation into Backus–Naur Form:
	http://chessprogramming.wikispaces.com/Extended+Position+Description
*/
package epd

import (
	"code.google.com/p/gochess/notation/fen"
	"code.google.com/p/gochess/notation/san"
	pos "code.google.com/p/gochess/position"
	"errors"
	"fmt"
	"strings"
)

const minNumberOfFields = 4

// All implemented operations.
const (
	AvoidMoves = "am"
	BestMoves  = "bm"
	ID         = "id"
	Evaluation = "ce" //  centipawn evaluation
	Comment0   = "c0"
	Comment1   = "c1"
	Comment2   = "c2"
	Comment3   = "c3"
	Comment4   = "c4"
	Comment5   = "c5"
	Comment6   = "c6"
	Comment7   = "c7"
	Comment8   = "c8"
	Comment9   = "c9"
)

type Operation interface {
	Name() string
	Value() pos.Move
	IsOk(pos.Move) bool
}

type avoidMove struct {
	name  string
	value pos.Move
}

func (this avoidMove) Name() string {
	return this.name
}

func (this avoidMove) Value() pos.Move {
	return this.value
}

func (this avoidMove) IsOk(move pos.Move) bool {
	return this.value != move
}

type bestMove struct {
	name  string
	value pos.Move
}

func (this bestMove) Name() string {
	return this.name
}

func (this bestMove) Value() pos.Move {
	return this.value
}
func (this bestMove) IsOk(move pos.Move) bool {
	return this.value == move
}

type Position struct {
	ID       string
	Comments []string
	// todo: allow more than one
	Operation Operation
}

// todo: make returned error more clear
func ParsePosition(epd string) (*pos.Position, error) {

	fields := strings.Split(epd, " ")
	if len(fields) < minNumberOfFields {
		return nil, errors.New(fmt.Sprintf("EPD must contain at least %v fields, found %v", minNumberOfFields, len(fields)))
	}

	activeColor, err := fen.ParseActiveColor(fields[1])
	if err != nil {
		return nil, err
	}

	castlingWhite, castlingBlack, err := fen.ParseCastlingAvailability(fields[2])
	if err != nil {
		return nil, err
	}

	enPassantTarget, err := fen.ParseEnPassantTarget(fields[3])
	if err != nil {
		return nil, err
	}

	position := pos.NewPosition(activeColor, enPassantTarget, castlingWhite, castlingBlack, 0)

	pieces, err := fen.ParsePieces(fields[0])
	if err != nil {
		return nil, err
	}
	for i := range pieces {
		position.Set(pieces[i].Piece, pieces[i].Location)
	}
	return position, nil
}

// todo: the function signature is bad designed
// todo: use bytes for epd
func ParseOperations(p *pos.Position, epd string, legalMoves []pos.Move) (*Position, error) {

	fields := strings.Split(epd, " ")
	position := new(Position)

	if len(fields) == minNumberOfFields {
		// no operations found
		return position, nil
	}

	ops := strings.Split(strings.Join(fields[minNumberOfFields:], " "), ";")
	// leave out last element. it's empty, as ";" is the last char in epd
	for i := range ops[:len(ops)-1] {
		op := strings.Split(strings.TrimSpace(ops[i]), " ")
		opCode, value := op[0], op[1]

		switch opCode {
		case AvoidMoves:
			move, err := san.ParseMove(p, value, legalMoves)
			if err != nil {
				// todo: make the error more clear
				return position, err
			}
			position.Operation = avoidMove{"avoid move", move}
		case BestMoves:
			move, err := san.ParseMove(p, value, legalMoves)
			if err != nil {
				// todo: make the error more clear
				return position, err
			}
			position.Operation = bestMove{"best move", move}
		case Comment0, Comment1, Comment2, Comment3, Comment4, Comment5,
			Comment6, Comment7, Comment8, Comment9:
			position.Comments = append(position.Comments, value)
		case ID:
			position.ID = value
		default:
			return position, errors.New(fmt.Sprintf("op code '%v' is not implemented", opCode))
		}
	}
	return position, nil
}
